#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> 
#include <sys/ioctl.h>

#define FREF					24000000
#define FFBD_FRAC_MAX				16777216

int main(int argc, char** argv)
{
	int j, k, i, nf = 0, nr = 1, tmds_no, tmds_a, tmds_b, tmds_c;
	int pclk_no, dclk_no, div_5 = 0;
	int tmdsa[4] = {1, 2, 3, 5};
	int tmdsbc[4] = {1, 2, 4, 8};
	int pclkb[4] = {2, 3, 4, 5};
	int pclkc[4] = {1, 2, 4, 8};
	int pclka, pclkd;
	int support_frac;
	/*
	 * In order to get the clock as accurately as possible,
	 * we need to use larger values for the calculation.
	 */
	unsigned long long remainder;
	unsigned long fvco, rate, tmdsclock;
	int ftmp, frac_cal = 0;

	rate = atol(argv[1]);
	tmdsclock = atol(argv[2]);
	support_frac = atoi(argv[3]);

	printf("rate:%lu,tmdsclock:%lu\n", rate, tmdsclock);

	if (rate > tmdsclock && rate < 340000000) {
		printf("can't support this yuv420 mode\n");
		return 0;
	}

	/* VCO frequency shall not be higher than 3.2Ghz */
	i = 3200000000UL / tmdsclock / 4 + 1;

continue_cal:
	/*
	 * If the current parameters can not get the correct clock,
	 * return here and continue to calculate with next set of
	 * parameters until the allowed range is exceeded or get
	 * the correct clock.
	 */
	for (; nr < 31; nr++) {
		ftmp = FREF / nr;
		nf = 0;
		/* VCO frequency shall not be lower than 1.4Ghz */
		while (((tmdsclock * 4 * (--i)) > 1400000000) && i > 0) {
			fvco = tmdsclock * 4 * i;
			div_5 = 0;
			remainder = fvco % ftmp;
			if (!frac_cal && !remainder && (fvco / ftmp <= 4096)) {
				nf = fvco / ftmp;
				tmds_no = i;
				break;
			} else if (frac_cal && (fvco / ftmp <= 4096)) {
				nf = fvco / ftmp;
				tmds_no = i;
				/*
				 * magnifying value 10000000000 times to
				 * simulate float calculation.
				 */
				remainder *= 1000000000;
				remainder = remainder / ftmp;
				remainder *= FFBD_FRAC_MAX;
				remainder = remainder / 1000000000;
				break;
			}
		}

		if (nf)
			break;

		i = 3200000000UL / tmdsclock / 4 + 1;
	}

	if (nr == 31) {
		if (support_frac == 1) {
			/*
			 * 3328 support fraction calculation. If this clk can't
			 * be calculated with integers, using fraction to
			 * calculate.
			 */
			if (!frac_cal) {
				frac_cal = 1;
				nr = 1;
				goto continue_cal;
			}
		}
		printf("can't support tmdsclock:%lu\n", tmdsclock);
		return 0;
	}

	if (tmdsclock > 340000000) {
		for (k = 0; k < 4; k++) {
			for (j = 2; j < 4; j++) {
				if (tmdsa[k] * tmdsbc[j] == (4 * tmds_no))
					break;
			}
			if (j < 4)
				break;
		}
	} else {
		for (k = 0; k < 4; k++) {
			for (j = 0; j < 4; j++) {
				if (tmdsa[k] * tmdsbc[j] == tmds_no)
					break;
			}
			if (j < 4)
				break;
		}
	}

	if (k == 4) {
		printf("tmdsclock can't be cal\n");
		nf = 0;
		goto continue_cal;
	}

	tmds_a = k;
	tmds_b = j;
	if (tmdsclock > 340000000)
		tmds_c = j - 2;
	else
		tmds_c = j;

	printf("tmds_a %d (%d) tmds_b %d (%d) tmds_c %d (%d)\n",
		tmds_a, tmdsa[tmds_a], tmds_b, tmdsbc[tmds_b], tmds_c,
		tmdsbc[tmds_c]);

	/* Only support 4k-60/50hz yuv420 clock */
	if (rate > tmdsclock) {
		if (fvco % (rate / 2)) {
			printf("can't get pclk_no\n");
			goto continue_cal;
		}

		pclk_no = fvco / (rate / 2);

		if ((fvco / rate) == 5) {
			div_5 = 1;
		} else {
			if (pclk_no % 4) {
				printf("can't get dclk_no\n");
				goto continue_cal;
			}
			dclk_no = pclk_no / 4;
		}
	} else {
		if (fvco % rate) {
			printf("can't get pclk_no\n");
			goto continue_cal;
		}
		pclk_no = fvco / rate;

		if ((fvco / rate) == 5) {
			div_5 = 1;
		} else {
			if (pclk_no % 2) {
				printf("can't get dclk_no\n");
				goto continue_cal;
			}
			dclk_no = pclk_no / 2;
		}
	}

	printf("dclk_no:%d,pclk_no:%d,div_5:%d\n",
		dclk_no, pclk_no, div_5);

	for (k = 0; k < 4; k++) {
		for (j = 0; j < 4; j++) {
			if (pclkb[k] * pclkc[j] == pclk_no)
				break;
		}
		if (j < 4)
			break;
	}

	if (k == 4) {
		for (j = 0; j < 4; j++) {
			if ((pclk_no % pclkc[j]) == 0 &&
			    (pclk_no / pclkc[j]) < 32) {
				pclka = pclk_no / pclkc[j];
				break;
			}
		}
	} else {
		pclka = 1;
	}

	if (j == 4) {
		printf("can't get pclk\n");
		goto continue_cal;
	}

	if (div_5) {
		pclkd = 1;
	} else {
		if (k == 4) {
			if (dclk_no % pclka)
				goto continue_cal;
			else
				pclkd = dclk_no / pclka;
		} else {
			if (dclk_no % pclkb[k])
				goto continue_cal;
			else
				pclkd = dclk_no / pclkb[k];
		}
	}

/*	if (cfg) {
		cfg->pixclock = rate;
		cfg->tmdsclock = tmdsclock;
		cfg->prediv = nr;
		cfg->fbdiv = nf;
		cfg->tmds_div_a = tmds_a;
		cfg->tmds_div_b = tmds_b;
		cfg->tmds_div_c = tmds_c;
		cfg->pclk_div_a = pclka;
		cfg->pclk_div_b = k;
		cfg->pclk_div_c = j;
		cfg->pclk_div_d = pclkd;
		cfg->vco_div_5_en = div_5;
		cfg->fracdiv = remainder;
	}
*/
	printf("%ld, %ld, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %lld\n",
	       rate, tmdsclock, nr, nf, tmds_a, tmds_b, tmds_c, pclka, k, j, pclkd, div_5, remainder);

	return rate;
}